/*
 $License:
    Copyright (C) 2012 Memsic INC., All Rights Reserved.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  $
 */

#ifdef __KERNEL__
#include <linux/module.h>
#include <linux/delay.h>
#endif

#include "mpu.h"
#include "mlsl.h"
#include "mlos.h"

#ifdef MPU_SENSORS_MMC3416X 
#define MMC3416x	        1	
#endif

static int reset_int = 1000;
static int read_count = 1;

#define MMC3416X_REG_CTRL             (0x07)
#define MMC3416X_REG_DS               (0x06)
#define MMC3416X_REG_DATA             (0x00)
#define MMC3416X_REG_BITS             (0x08)
#define MMC3416X_REG_PRODUCTID0       (0x10)
#define MMC3416X_REG_PRODUCTID1       (0x20)

#define MMC3416X_CTRL_TM	            (0x01)
#define MMC3416X_CTRL_CM              (0x02)

#define MMC3416X_CTRL_50HZ            (0x00)
#define MMC3416X_CTRL_25HZ            (0x04)
#define MMC3416X_CTRL_12HZ            (0x08)
#define MMC3416X_CTRL_NOBOOST         (0x10)
#define MMC3416X_CTRL_SET  	      (0x20)
#define MMC3416X_CTRL_RESET           (0x40)
#define MMC3416X_CTRL_REFILL          (0x80)

#define MMC3416X_BITS_SLOW_16         (0x00)
#define MMC3416X_BITS_FAST_16         (0x01)
#define MMC3416X_BITS_14              (0x02)

#define MMC3416X_DS_TM_READY	      (0x01)

#define MMC3416X_DELAY_TM	      (8)	/* ms */
#define MMC3416X_DELAY_SET	      (50)	/* ms */
#define MMC3416X_DELAY_RESET      (50)     /* ms */

int mmc3416x_suspend(void *mlsl_handle,
		    struct ext_slave_descr *slave,
		    struct ext_slave_platform_data *pdata)
{
	int result = INV_SUCCESS;
	// after data transfer, MMC sleeps automatically

	return result;
}

int mmc3416x_resume(void *mlsl_handle,
		   struct ext_slave_descr *slave,
		   struct ext_slave_platform_data *pdata)
{

	int result;
	result = inv_serial_single_write(mlsl_handle,
                                     pdata->address,
                                     MMC3416X_REG_CTRL,
                                     MMC3416X_CTRL_TM);
	ERROR_CHECK(result);

	read_count = 1;
	return INV_SUCCESS;
}

int mmc3416x_read(void *mlsl_handle,
		 struct ext_slave_descr *slave,
		 struct ext_slave_platform_data *pdata,
		 unsigned char *data)
{
	int result;
	unsigned char status = 0;
	int md_times = 0;

	if (read_count > 1000)
		read_count = 1;

    //instruct a measurement
    result = inv_serial_single_write(mlsl_handle,
                                     pdata->address,
                                     MMC3416X_REG_CTRL,
                                     MMC3416X_CTRL_TM);
	msleep(MMC3416X_DELAY_TM);

    //wait for the measurement is done
    do{
        result = inv_serial_read(mlsl_handle,
                                 pdata->address,
                                 MMC3416X_REG_DS,
                                 1, &status);
        ERROR_CHECK(result);

        md_times++;
        if (md_times > 3)
            return INV_ERROR_COMPASS_DATA_NOT_READY;
        msleep(1);
    } while ((status & MMC3416X_DS_TM_READY) != MMC3416X_DS_TM_READY);

	result =
	    inv_serial_read(mlsl_handle, pdata->address, MMC3416X_REG_DATA,
			    6, (unsigned char *)data);
	ERROR_CHECK(result);

	{
        short tmp[3];
        unsigned char tmpdata[6];
        int ii;

        for (ii = 0; ii < 6; ii++)
            tmpdata[ii] = data[ii];

        for (ii = 0; ii < 3; ii++) {
            tmp[ii] = (short)((tmpdata[2 * ii + 1 ] << 8) + tmpdata[2 * ii]);
            tmp[ii] = tmp[ii] - 32768;
            tmp[ii] = tmp[ii] * 2;   // ??? double check with INVN
        }

        for (ii = 0; ii < 3; ii++) {
            data[2 * ii] = (unsigned char)(tmp[ii] >> 8);
            data[2 * ii + 1] = (unsigned char)(tmp[ii]);
        }

    }

    // reset the RM periodically
	if (read_count % reset_int == 0) {
        result =
            inv_serial_single_write(mlsl_handle,
                        pdata->address,
                        MMC3416X_REG_CTRL,
                        MMC3416X_CTRL_REFILL);
        ERROR_CHECK(result);
        msleep(MMC3416X_DELAY_SET);
	  result =
            inv_serial_single_write(mlsl_handle,
                        pdata->address,
                        MMC3416X_REG_CTRL,
                        MMC3416X_CTRL_SET);
        ERROR_CHECK(result);
        msleep(1);
	  result =
            inv_serial_single_write(mlsl_handle,
                        pdata->address,
                        MMC3416X_REG_CTRL,
                        0);
        ERROR_CHECK(result);
        msleep(1);
        read_count++;
        return INV_ERROR_COMPASS_DATA_NOT_READY;
	}

	ERROR_CHECK(result);
	read_count++;

	return INV_SUCCESS;
}

struct ext_slave_descr mmc3416x_descr = {
	/*.init             = */ NULL,
	/*.exit             = */ NULL,
	/*.suspend          = */ mmc3416x_suspend,
	/*.resume           = */ mmc3416x_resume,
	/*.read             = */ mmc3416x_read,
	/*.config           = */ NULL,
	/*.get_config       = */ NULL,
	/*.name             = */ "mmc3416x",
	/*.type             = */ EXT_SLAVE_TYPE_COMPASS,
	/*.id               = */ COMPASS_ID_MMC3416X,
	/*.reg              = */ 0x01,
	/*.len              = */ 6,
	/*.endian           = */ EXT_SLAVE_BIG_ENDIAN,
	/*.range            = */ {400, 0},
	/*.trigger          = */ NULL,
};

struct ext_slave_descr *mmc3416x_get_slave_descr(void)
{
	return &mmc3416x_descr;
}
EXPORT_SYMBOL(mmc3416x_get_slave_descr);

/**
 *  @}
 */
